Skip to content

Revert "Revert "backend/feat: Stripe payout dynamic transfer top-up f…#1326

Merged
khuzema786 merged 1 commit into
mainfrom
backend/reverting/reverting/pr
Jun 9, 2026
Merged

Revert "Revert "backend/feat: Stripe payout dynamic transfer top-up f…#1326
khuzema786 merged 1 commit into
mainfrom
backend/reverting/reverting/pr

Conversation

@karan2cmd

@karan2cmd karan2cmd commented Jun 2, 2026

Copy link
Copy Markdown
Member

…or Fleet VA insufficient balance (#1320)""

This reverts commit ce882b3.

Type of Change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring
  • Dependency updates

Description

Additional Changes

  • This PR modifies the database schema (database migration added)
  • This PR modifies dhall configs/environment variables

Motivation and Context

How did you test it?

Checklist

  • I formatted the code and addressed linter errors ./dev/format-all-files.sh
  • I reviewed submitted code
  • I added unit tests for my changes where possible
  • I added a CHANGELOG entry if applicable

Summary by CodeRabbit

  • New Features
    • Payouts now compute adjusted transfer amounts when fleet funds are insufficient, with optional merchant top-ups to cover shortfalls.
    • Payout responses include a new optional merchantTopUpAmount field when a top-up occurs.
    • Balance retrieval and currency-available calculations added for Stripe to validate funds before processing transfers.

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b45811a5-2f32-4a20-9717-6682219ce5ec

📥 Commits

Reviewing files that changed from the base of the PR and between ae9eed3 and 173b932.

📒 Files selected for processing (5)
  • lib/mobility-core/src/Kernel/External/Payout/Interface.hs
  • lib/mobility-core/src/Kernel/External/Payout/Interface/Juspay.hs
  • lib/mobility-core/src/Kernel/External/Payout/Interface/Stripe.hs
  • lib/mobility-core/src/Kernel/External/Payout/Interface/Types.hs
  • lib/mobility-core/src/Kernel/External/Payout/Stripe/Flow.hs
🚧 Files skipped from review as they are similar to previous changes (5)
  • lib/mobility-core/src/Kernel/External/Payout/Interface/Juspay.hs
  • lib/mobility-core/src/Kernel/External/Payout/Interface/Types.hs
  • lib/mobility-core/src/Kernel/External/Payout/Interface/Stripe.hs
  • lib/mobility-core/src/Kernel/External/Payout/Stripe/Flow.hs
  • lib/mobility-core/src/Kernel/External/Payout/Interface.hs

Walkthrough

Adds Stripe balance retrieval and per-currency availability, computes an adjusted transfer (merchant top-up) when fleet funds are insufficient, exposes helpers to fetch available funds, adds merchantTopUpAmount :: Maybe HighPrecMoney to responses, and updates Juspay responses to include the new field.

Changes

Stripe Merchant Top-Up Payout Flow

Layer / File(s) Summary
Stripe balance types and API endpoint
lib/mobility-core/src/Kernel/External/Payout/Stripe/Flow.hs
BalanceFund and BalanceResp model Stripe balance structure; GetBalanceAPI defines GET /v1/balance; getBalance calls Stripe via PaymentFlow.
Balance query helpers in payout interface
lib/mobility-core/src/Kernel/External/Payout/Interface/Stripe.hs
Exports getBalance (decrypts API key and delegates) and getAvailableForCurrency (filters available entries by currency and sums converted amounts).
Payout response schema extension
lib/mobility-core/src/Kernel/External/Payout/Interface/Types.hs
Adds merchantTopUpAmount :: Maybe HighPrecMoney to CreatePayoutOrderResp (affects JSON/Schema).
Stripe payout adjusted transfer computation
lib/mobility-core/src/Kernel/External/Payout/Interface.hs
Stripe createPayoutOrder computes adjustedTransferAmount by querying fleet and merchant balances, validates merchant sufficiency, logs top-up, uses adjusted amount for transfer, and propagates merchantTopUpAmount; payoutOrderStatus sets it to Nothing.
Juspay payout response field population
lib/mobility-core/src/Kernel/External/Payout/Interface/Juspay.hs
createPayoutOrder and payoutOrderStatus now populate merchantTopUpAmount = Nothing in CreatePayoutOrderResp.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • khuzema786
  • Vignesh-772

Poem

🐰 I checked the balance, found a gap so small,
Platform lends a hop to cover it all.
Transfers adjusted, records set right,
Payouts now balance by moonlit night.
🌙✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is incomplete and appears truncated, ending with 'f…' without clearly conveying what the PR accomplishes or which feature is being reverted. Complete the title to clearly describe the feature being reverted, e.g., 'Revert "Revert "backend/feat: Stripe payout dynamic transfer top-up"' or provide the full feature name.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch backend/reverting/reverting/pr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot requested review from Vignesh-772 and khuzema786 June 2, 2026 12:27

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
lib/mobility-core/src/Kernel/External/Payout/Interface.hs (1)

47-74: 💤 Low value

Extra synchronous Stripe balance calls on every payout.

computeAdjustedTransfer always calls Stripe.getBalance for the fleet account (plus a second call for the platform account on shortfall) on the payout request path. This adds external-call latency and rate-limit pressure to every payout. Consider short-TTL caching of balances or gating the top-up logic behind a config flag if it isn't needed for all flows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/mobility-core/src/Kernel/External/Payout/Interface.hs` around lines 47 -
74, The payout path currently calls Stripe.getBalance inside
computeAdjustedTransfer for every request (fleetBalance and potentially
merchantBalance), causing extra latency and rate-limit pressure; update
computeAdjustedTransfer to first consult a short-TTL in-memory cache (keyed by
connectedAccountId and currency) for fleetBalance and merchantBalance and only
call Stripe.getBalance when the cache is missing/stale, and also gate the top-up
check behind a config flag (eg. enableTransferTopUp) so when disabled
computeAdjustedTransfer returns (req.transferAmount, Nothing) without any Stripe
calls; reference computeAdjustedTransfer, Stripe.getBalance,
fleetBalance/fleetAvail, merchantBalance/merchantAvail and merchantTopUpAmount
when making these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/mobility-core/src/Kernel/External/Payout/Interface.hs`:
- Around line 58-64: Replace the client-facing error that leaks merchantAvail
and adjustedAmt by logging the exact balances internally and throwing a generic
InvalidRequest message; specifically, in the branch that checks "when
(merchantAvail < adjustedAmt)" record merchantAvail and adjustedAmt to your
internal logger at an appropriate level (e.g., error/debug) and then call
"throwError $ InvalidRequest" with a non-sensitive message like "Merchant
platform account has insufficient balance" (omit numeric details).

In `@lib/mobility-core/src/Kernel/External/Payout/Interface/Stripe.hs`:
- Around line 152-155: getAvailableForCurrency is converting Stripe minor-unit
amounts with the hardcoded centsToUsd (/100), which breaks currencies like INR
that have different decimal accuracy; replace the hardcoded conversion by using
the currency's accuracy (e.g. getAccuracy or existing currency-decimal helper)
to compute scale = 10 ^ accuracy and convert each f.amount from minor units to
HighPrecMoney by dividing by that scale (instead of using centsToUsd), keeping
the currency filter on f.currency; update any helper conversions
(usdToCents/eurToCents) or replace them with a generic minorUnit <-> money
converter so fleetAvail and the transferAmount comparison use the correct scale.

---

Nitpick comments:
In `@lib/mobility-core/src/Kernel/External/Payout/Interface.hs`:
- Around line 47-74: The payout path currently calls Stripe.getBalance inside
computeAdjustedTransfer for every request (fleetBalance and potentially
merchantBalance), causing extra latency and rate-limit pressure; update
computeAdjustedTransfer to first consult a short-TTL in-memory cache (keyed by
connectedAccountId and currency) for fleetBalance and merchantBalance and only
call Stripe.getBalance when the cache is missing/stale, and also gate the top-up
check behind a config flag (eg. enableTransferTopUp) so when disabled
computeAdjustedTransfer returns (req.transferAmount, Nothing) without any Stripe
calls; reference computeAdjustedTransfer, Stripe.getBalance,
fleetBalance/fleetAvail, merchantBalance/merchantAvail and merchantTopUpAmount
when making these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2662eb71-b341-4695-83d8-22d4b60fa664

📥 Commits

Reviewing files that changed from the base of the PR and between ce882b3 and ae9eed3.

📒 Files selected for processing (5)
  • lib/mobility-core/src/Kernel/External/Payout/Interface.hs
  • lib/mobility-core/src/Kernel/External/Payout/Interface/Juspay.hs
  • lib/mobility-core/src/Kernel/External/Payout/Interface/Stripe.hs
  • lib/mobility-core/src/Kernel/External/Payout/Interface/Types.hs
  • lib/mobility-core/src/Kernel/External/Payout/Stripe/Flow.hs

Comment on lines +58 to +64
when (merchantAvail < adjustedAmt) $
throwError $
InvalidRequest $
"Merchant platform account has insufficient balance. Available: "
<> show merchantAvail
<> ", Required: "
<> show adjustedAmt

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't leak the merchant platform balance in a client-facing error.

InvalidRequest is surfaced to the API caller. Embedding the platform/merchant account's available balance (and exact required amount) in the message discloses internal financial state to whoever triggers the payout. Log the details internally and return a generic message.

🔒 Proposed fix
-              when (merchantAvail < adjustedAmt) $
-                throwError $
-                  InvalidRequest $
-                    "Merchant platform account has insufficient balance. Available: "
-                      <> show merchantAvail
-                      <> ", Required: "
-                      <> show adjustedAmt
+              when (merchantAvail < adjustedAmt) $ do
+                logError $
+                  "Merchant platform account has insufficient balance. Available: "
+                    <> show merchantAvail
+                    <> ", Required: "
+                    <> show adjustedAmt
+                throwError $
+                  InvalidRequest "Insufficient merchant platform account balance to complete payout."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/mobility-core/src/Kernel/External/Payout/Interface.hs` around lines 58 -
64, Replace the client-facing error that leaks merchantAvail and adjustedAmt by
logging the exact balances internally and throwing a generic InvalidRequest
message; specifically, in the branch that checks "when (merchantAvail <
adjustedAmt)" record merchantAvail and adjustedAmt to your internal logger at an
appropriate level (e.g., error/debug) and then call "throwError $
InvalidRequest" with a non-sensitive message like "Merchant platform account has
insufficient balance" (omit numeric details).

Comment on lines +152 to +155
getAvailableForCurrency :: Currency -> Stripe.BalanceResp -> HighPrecMoney
getAvailableForCurrency currency Stripe.BalanceResp {available} =
let currencyText = T.toLower $ show currency
in sum [centsToUsd f.amount | f <- available, f.currency == currencyText]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the cents/major-unit converters to confirm scale is currency-agnostic.
ast-grep --pattern 'centsToUsd $_ = $$$'
ast-grep --pattern 'eurToCents $_ = $$$'
ast-grep --pattern 'usdToCents $_ = $$$'
rg -nP --type=haskell -C2 '\b(centsToUsd|eurToCents|usdToCents)\b'

Repository: nammayatri/shared-kernel

Length of output: 12663


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the Interface.hs referenced by the review comment
fd -e hs -a Interface.hs lib/mobility-core/src | head -n 50

# Search for the specific comparison expression
rg -n --type=haskell "fleetAvail\s*\+\s*req\.transferAmount\s*>?=\s*req\.amount" lib/mobility-core/src || true

# If not found, search for fleetAvail usage near transferAmount/amount
rg -n --type=haskell "fleetAvail" lib/mobility-core/src | head -n 50

Repository: nammayatri/shared-kernel

Length of output: 2691


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="lib/mobility-core/src/Kernel/External/Payout/Interface.hs"

# Show the relevant block around the comparison
nl -ba "$FILE" | sed -n '1,140p'

# Find the request record type that contains transferAmount/amount
rg -n --type=haskell "transferAmount" lib/mobility-core/src/Kernel/External/Payout/Interface.hs
rg -n --type=haskell "data .*Transfer.*|data .*Payout.*|record.*Transfer|req\.transferAmount" lib/mobility-core/src/Kernel/External/Payout/Interface.hs

# Broader search for where req.amount / transferAmount are built for payout
rg -n --type=haskell "transferAmount\s*=\s*" lib/mobility-core/src/Kernel/External/Payout lib/mobility-core/src/Kernel/External -S
rg -n --type=haskell "req\.amount" lib/mobility-core/src/Kernel/External/Payout -S

Repository: nammayatri/shared-kernel

Length of output: 111


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="lib/mobility-core/src/Kernel/External/Payout/Interface.hs"

# Line-numbered view without nl
cat -n "$FILE" | sed -n '1,120p'

# Find request/type definitions and uses
rg -n --type=haskell "transferAmount" "$FILE"
rg -n --type=haskell "req\.amount|req\.transferAmount|amount\s*=|transferAmount\s*=" lib/mobility-core/src/Kernel/External/Payout -S
rg -n --type=haskell "data .*Payout|data .*Transfer|Create.*Payout|Transfer.*Req|Payout.*Req|record .*Payout" lib/mobility-core/src/Kernel/External/Payout -S

Repository: nammayatri/shared-kernel

Length of output: 25372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect payout request types
rg -n --type=haskell "data CreatePayoutOrderReq" lib/mobility-core/src/Kernel/External/Payout/Interface/Types.hs
cat -n lib/mobility-core/src/Kernel/External/Payout/Interface/Types.hs | sed -n '1,220p'

# Inspect Stripe payout conversion of req.amount/transferAmount to Stripe minor units
cat -n lib/mobility-core/src/Kernel/External/Payout/Interface/Stripe.hs | sed -n '1,220p'

# Find Currency definition and any decimal/minor-unit scaling logic
rg -n --type=haskell "data Currency|newtype Currency|type Currency" lib/mobility-core/src/Kernel | head -n 50
rg -n --type=haskell "Currency" lib/mobility-core/src/Kernel | rg -n "decim|minor|zero|scale|fraction" | head -n 50 || true
rg -n --type=haskell "zero[-_ ]?decimal|minor unit|decimal" lib/mobility-core/src/Kernel | head -n 50 || true

Repository: nammayatri/shared-kernel

Length of output: 19012


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n --type=haskell "HighPrecMoney" lib/mobility-core/src/Kernel/Types/Price.hs lib/mobility-core/src/Kernel | head -n 50
rg -n --type=haskell "newtype HighPrecMoney|type HighPrecMoney" lib/mobility-core/src/Kernel | head -n 50
rg -n --type=haskell "HighPrecMoney.*cents|cents.*HighPrecMoney|minor.*HighPrecMoney" lib/mobility-core/src/Kernel | head -n 50

Repository: nammayatri/shared-kernel

Length of output: 6407


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="lib/mobility-core/src/Kernel/Types/Price.hs"

# show around HighPrecMoney definition
sed -n '1,140p' "$FILE" | cat -n | sed -n '40,110p'
# show rest where cents/minor may be defined
sed -n '140,260p' "$FILE" | cat -n | head -n 160

Repository: nammayatri/shared-kernel

Length of output: 8353


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --type=haskell "getAvailableForCurrency|centsToUsd|usdToCents|eurToCents" lib/mobility-core/src/Kernel/External/Payout/Interface/Stripe.hs lib/mobility-core/src/Kernel/External/Payout/Interface.hs lib/mobility-core/src/Kernel/External/Payment/Interface/Stripe.hs
rg -n --type=haskell "getAccuracy\s+INR|data Currency\s*=\s*INR\s*\|" lib/mobility-core/src/Kernel/Types/Price.hs

Repository: nammayatri/shared-kernel

Length of output: 4628


Fix currency minor-unit conversion in getAvailableForCurrency (INR is broken by hardcoded /100).

getAvailableForCurrency filters by f.currency == currencyText, but still converts every matching Stripe amount with centsToUsd:

getAvailableForCurrency :: Currency -> Stripe.BalanceResp -> HighPrecMoney
getAvailableForCurrency currency Stripe.BalanceResp {available} =
  let currencyText = T.toLower $ show currency
   in sum [centsToUsd f.amount | f <- available, f.currency == currencyText]

centsToUsd is hardcoded to / 100, and both usdToCents/eurToCents are hardcoded to * 100—so for Currency = INR (0-decimal in getAccuracy INR = 0), fleetAvail (and the top-up logic guarded by fleetAvail + req.transferAmount >= req.amount in Kernel/External/Payout/Interface.hs) will use the wrong scale.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/mobility-core/src/Kernel/External/Payout/Interface/Stripe.hs` around
lines 152 - 155, getAvailableForCurrency is converting Stripe minor-unit amounts
with the hardcoded centsToUsd (/100), which breaks currencies like INR that have
different decimal accuracy; replace the hardcoded conversion by using the
currency's accuracy (e.g. getAccuracy or existing currency-decimal helper) to
compute scale = 10 ^ accuracy and convert each f.amount from minor units to
HighPrecMoney by dividing by that scale (instead of using centsToUsd), keeping
the currency filter on f.currency; update any helper conversions
(usdToCents/eurToCents) or replace them with a generic minorUnit <-> money
converter so fleetAvail and the transferAmount comparison use the correct scale.

…or Fleet VA insufficient balance (#1320)""

This reverts commit ce882b3.
@karan2cmd karan2cmd force-pushed the backend/reverting/reverting/pr branch from ae9eed3 to 173b932 Compare June 8, 2026 08:40
@khuzema786 khuzema786 merged commit 6093259 into main Jun 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants